<?php
declare (strict_types = 1);

namespace app\service\basic\auth;

use app\exception\LogicException;
use Carbon\Carbon;
use Exception;
use thans\jwt\exception\BadMethodCallException;
use think\Model;
use thans\jwt\JWTAuth;
use thans\jwt\Blacklist;
use app\exception\AuthException;
use thans\jwt\exception\TokenBlacklistException;

class JWTGuard implements Guard
{
    use GuardHelpers;

    /**
     * The user we last attempted to retrieve.
     *
     */
    protected $lastAttempted;

    /**
     * The JWT instance.
     */
    protected $jwt;

    /**
     * JWT platform
     *
     * @var
     */
    protected $platform;

    public function __construct(JWTAuth $jwt, UserProvider $provider, $platform)
    {
        $this->jwt = $jwt;
        $this->provider = $provider;
        $this->platform = $platform;
    }

    /**
     * 用户信息
     *
     * @return bool|Model
     */
    public function user()
    {
        if ($this->user !== null) {
            return $this->user;
        }

        try {
            if ($payload = $this->check()) {
                $user = $this->provider->retrieveById($payload['user']->getValue()->id);

                if (!$user->isExists()) {
                    return false;
                }

                if (!$this->provider->validateState($user)) {
                    return false;
                }
                $this->user = $user;
            }
        } catch (Exception $e) {
            return false;
        }

        return $this->user;
    }

    /**
     * 用户信息, 失败抛异常
     *
     * @return Model
     * @throws AuthException
     */
    public function userOrFail()
    {
        if ($this->user !== null) {
            return $this->user;
        }

        if ($payload = $this->check()) {
            $user = $this->provider->retrieveById($payload['user']->getValue()->id);

            if (!$user->isExists()) {
                throw new AuthException(lang('communal.account not found'));
            }

            if (!$this->provider->validateState($user)) {
                throw new AuthException(lang('communal.account state disable'));
            }

            $this->user = $user;
        }

        return $this->user;
    }

    /**
     * 当前token
     *
     * @return mixed
     */
    public function token()
    {
        return $this->jwt->token();
    }

    /**
     * token检测
     *
     * @return array|bool
     * @throws AuthException
     */
    public function check()
    {
        try {
            $payload = $this->jwt->auth();
            if ($this->platform != $payload['platform']->getValue()) {
                throw new AuthException(lang('communal.jwt token error'), 401);
            }
            return $payload;
        } catch (Exception $e) {
            throw new AuthException(lang('communal.jwt token error'), 401);
        }
    }

    /**
     * Attempt to authenticate the user using the given credentials and return the token.
     *
     * @param array $credentials
     * @param bool $login
     * @return bool|string
     * @throws LogicException
     */
    public function attempt(array $credentials = [], $login = true)
    {
        $user = $this->provider->retrieveByCredentials($credentials);
        if (!$user->isExists()) {
            throw new LogicException(lang('communal.account not found'));
        }

        if (!$this->provider->validateState($user)) {
            throw new LogicException(lang('communal.account state disable'));
        }

        $this->lastAttempted = $user;

        if ($this->hasValidCredentials($user, $credentials)) {
            return $login ? $this->login($user) : true;
        } else {
            throw new LogicException(lang('communal.password not found'));
        }
    }

    /**
     * Create a token for a user.
     *
     * @param  Model $user
     *
     * @return string
     */
    public function login($user)
    {
        $builder['platform'] = $this->platform;
        $builder['user'] = $user->getAuthTokenGenerateData();
        $builder['timestamp'] = Carbon::now()->timestamp;
        $token = $this->jwt->builder($builder);
        $this->setUser($user);

        return $token;
    }

    /**
     * 退出
     *
     * @return Blacklist
     * @throws TokenBlacklistException
     */
    public function logout()
    {
        return $this->jwt->invalidate($this->jwt->token());
    }

    /**
     * token加入黑名单
     *
     * @param $token
     * @return Blacklist
     * @throws TokenBlacklistException
     */
    public function invalidate($token)
    {
        return $this->jwt->invalidate($token);
    }

    /**
     * Get the last user we attempted to authenticate.
     *
     * @return mixed
     */
    public function getLastAttempted()
    {
        return $this->lastAttempted;
    }

    /**
     * Set the current user.
     *
     * @param Model $user
     * @return $this
     */
    public function setUser($user)
    {
        $this->user = $user;

        return $this;
    }

    /**
     * Determine if the user matches the credentials.
     *
     * @param  mixed  $user
     * @param  array  $credentials
     *
     * @return bool
     */
    public function hasValidCredentials($user, $credentials)
    {
        return $user !== null && $this->provider->validateCredentials($user, $credentials);
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     * @throws BadMethodCallException
     */
    public function __call($name, $arguments)
    {
        if (method_exists($this->jwt, $name)) {
            return call_user_func_array([$this->jwt, $name], $arguments);
        }

        throw new BadMethodCallException("Method [$name] does not exist.");
    }
}
